package nimbus.commands
{
	import nimbus.commands.base.ICommand;
	import nimbus.events.ExecutionEvent;
	
	/**
	 * The command base to use when a group of subcommands should be processed in sequence.
	 * The subcommands passed through the constructor will be processed in sequence and the command
	 * will complete execution once all the subcommands have completed execution.
	 * 
	 * <p>This command may be used as a subcommand within another ParallelCommand or 
	 * SequenceCommand.</p>
	 * 
	 * @see nimbus.commands.ParallelCommand
	 */
	public class SequenceCommand extends AsyncCommand
	{
		/**
		 * Whether subsequent subcommands should be executed if a subcommand fails.
		 * 
		 * @default false
		 */
		public var atomic:Boolean = false;
		
		/**
		 * Whether to use subcommand processing messages to provide user feedback.
		 * 
		 * @see nimbus.Controller#deriveProcessingMessage()
		 * 
		 * @default false
		 */
		public var useSubcommandProcessingMessages:Boolean = false;
		
		/**
		 * The subcommands to be executed in sequence.
		 */
		protected var subcommands:Array;
		
		/**
		 * The subcommands still in the process of executing.
		 */
		protected var subcommandsQueue:Array;
		
		/**
		 * Whether all subcommands executed successfully.
		 */
		protected var subcommandsSuccessful:Boolean = true;
		
		/**
		 * Constructor.
		 * 
		 * @param subcommands The group of subcommands to be executed in sequence.
		 */
		public function SequenceCommand(subcommands:Array=null)
		{
			this.subcommands = subcommands; 
		}
		
		/**
		 * Executes all subcommands in sequence.
		 */
		override public function execute():void
		{
			if (subcommands && subcommands.length > 0)
			{
				subcommandsQueue = subcommands.slice();
				executeNextSubcommand();
			}
			else
			{
				sequenceComplete(true);
			}
		}
		
		/**
		 * Executes the next command in the queue.
		 */
		protected function executeNextSubcommand():void
		{
			if (subcommandsQueue && subcommandsQueue.length > 0)
			{
				var subcommand:ICommand = subcommandsQueue[0];
				
				if (subcommand is SequenceCommand)
				{
					SequenceCommand(subcommand).useSubcommandProcessingMessages = 
							useSubcommandProcessingMessages;
				} else if (subcommand is ParallelCommand)
				{
					ParallelCommand(subcommand).useSubcommandProcessingMessages =
							useSubcommandProcessingMessages;
				}
				
				if (subcommand is AsyncCommand)
				{
					AsyncCommand(subcommand).addEventListener(ExecutionEvent.EXECUTION_COMPLETE,
							asyncSubcommandComplete);
					AsyncCommand(subcommand).execute();
					
					if (useSubcommandProcessingMessages)
					{
						deriveProcessingMessage(AsyncCommand(subcommand));
					}
				}
				else
				{
					try
					{
						subcommand.execute();
					}
					catch (error:Error)
					{
						subcommandComplete(false);
						throw error;
					}
					subcommandComplete(true);
				}
			}
			else
			{
				sequenceComplete(subcommandsSuccessful);
			}
		}
		
		/**
		 * Handles asynchronous command completion.  Cleans up listeners.
		 */
		protected function asyncSubcommandComplete(event:ExecutionEvent):void
		{
			AsyncCommand(event.target).removeEventListener(ExecutionEvent.EXECUTION_COMPLETE,
						asyncSubcommandComplete);
			subcommandComplete(event.success);
		}
		
		/**
		 * Called each time a subcommand completes execution.
		 */
		protected function subcommandComplete(success:Boolean):void
		{
			if (!success)
			{
				subcommandsSuccessful = false;
			}
			
			subcommandsQueue.shift();
			
			// If the subcommand was not successful, only move to the next command if atomic is
			// set to true.
			if (success || atomic)
			{
				executeNextSubcommand();
			}
			else
			{
				sequenceComplete(false);
			}
		}
		
		/**
		 * Called once all subcommands have completed execution.  The extending class may override
		 * this function to execute any last-minute logic before the command completes execution.
		 */ 
		protected function sequenceComplete(success:Boolean):void
		{
			if (success)
			{
				dispatchSuccess();
			}
			else
			{
				dispatchFailure();
			}
		}
		
		/**
		 * Analyzes currently executing subcommands to determine which processing message should
		 * be used to provide feedback to the user.  The first processing message found in the
		 * list of processing subcommands will be used.
		 * 
		 * @see nimbus.Controller#deriveProcessingMessage()
		 */
		protected function deriveProcessingMessage(command:AsyncCommand):void
		{
			processingMessage = command.processingMessage;
		}
	}
}